Creates an object structure for a boiler, describes its mapping into OPC Unified Architecture server using attributes, and then performs the live mapping. Boiler data is then read, written and/or subscribed to using plain .NET object access..
The main program:
// UAConsoleLiveMapping: Creates an object structure for a boiler, describes its mapping into OPC Unified Architecture server // using attributes, and then performs the live mapping. Boiler data is then read, written and/or subscribed to using plain .NET // object access. // // Find all latest examples here: https://opclabs.doc-that.com/files/onlinedocs/OPCLabs-OpcStudio/Latest/examples.html . using System; using System.Diagnostics; using System.Threading; using OpcLabs.EasyOpc.UA; using OpcLabs.EasyOpc.UA.LiveMapping; using OpcLabs.EasyOpc.UA.LiveMapping.Extensions; using OpcLabs.EasyOpc.UA.Navigation; using OpcLabs.EasyOpc.UA.OperationModel; namespace UAConsoleLiveMapping { class Program { static void Main() { // the OPC server UAEndpointDescriptor endpointDescriptor = "opc.tcp://opcua.demo-this.com:51210/UA/SampleServer"; // or "http://opcua.demo-this.com:51211/UA/SampleServer" (currently not supported) // or "https://opcua.demo-this.com:51212/UA/SampleServer/" Console.WriteLine(); Console.WriteLine("Mapping our data structures to OPC..."); var mapper = new UAClientMapper(); var boiler1 = new Boiler(); mapper.Map(boiler1, new UAMappingContext { EndpointDescriptor = endpointDescriptor, // The NodeDescriptor below determines where in the OPC address space we want to map our data to. NodeDescriptor = new UANodeDescriptor { // '#' is a reserved character in a browse name, and must be escaped by '&' in the path below. BrowsePath = UABrowsePath.Parse("[ObjectsFolder]/Boilers/Boiler ", "http://opcfoundation.org/UA/Boiler/") }, MonitoringParameters = 1000, // requested sampling interval (for subscriptions) }); Console.WriteLine(); Console.WriteLine("Starting the simulation of the boiler in the server, using an OPC method call..."); // Currently there is no live mapping for OPC methods, therefore we call the OPC method in a traditional way. try { EasyUAClient.SharedInstance.CallMethod( endpointDescriptor, UABrowsePath.Parse("[ObjectsFolder]/Boilers/Boiler /Simulation", "http://opcfoundation.org/UA/Boiler/"), UABrowsePath.Parse("[nsu=http://opcfoundation.org/UA/Boiler/;i=1287].Start", "http://opcfoundation.org/UA/")); } catch (UAException) { // Production code would test the current state of the simulation first, and also handle the exception here. } Console.WriteLine(); Console.WriteLine("Reading all data of the boiler..."); mapper.Read(); Console.WriteLine($"Drum level is: {boiler1.Drum.LevelIndicator.Output}"); Console.WriteLine(); Console.WriteLine("Writing new setpoint value..."); boiler1.LevelController.SetPoint = 50.0; Debug.Assert(!(boiler1.LevelController is null)); mapper.WriteTarget(boiler1.LevelController, /*recurse:*/false); Console.WriteLine(); Console.WriteLine("Subscribing to boiler data changes..."); mapper.Subscribe(/*active:*/true); Thread.Sleep(30 * 1000); Console.WriteLine(); Console.WriteLine("Unsubscribing from boiler data changes..."); mapper.Subscribe(/*active:*/false); Console.WriteLine(); Console.WriteLine("Press Enter to continue..."); Console.ReadLine(); } } }
' UAConsoleLiveMapping: Creates an object structure for a boiler, describes its mapping into OPC Unified Architecture server ' using attributes, and then performs the live mapping. Boiler data is then read, written and/or subscribed to using plain .NET ' object access. ' ' Find all latest examples here: https://opclabs.doc-that.com/files/onlinedocs/OPCLabs-OpcStudio/Latest/examples.html . Imports System.Threading Imports OpcLabs.EasyOpc.UA Imports OpcLabs.EasyOpc.UA.LiveMapping Imports OpcLabs.EasyOpc.UA.LiveMapping.Extensions Imports OpcLabs.EasyOpc.UA.Navigation Imports OpcLabs.EasyOpc.UA.OperationModel Friend Class Program Shared Sub Main() ' Define which server we will work with. Dim endpointDescriptor As UAEndpointDescriptor = "opc.tcp://opcua.demo-this.com:51210/UA/SampleServer" ' or "http://opcua.demo-this.com:51211/UA/SampleServer" (currently not supported) ' or "https://opcua.demo-this.com:51212/UA/SampleServer/" Console.WriteLine() Console.WriteLine("Mapping our data structures to OPC...") Dim mapper = New UAClientMapper() Dim boiler1 = New Boiler() ' The NodeDescriptor below determines where in the OPC address space we want to map our data to. ' '#' is a reserved character in a browse name, and must be escaped by '&' in the path below. mapper.Map(boiler1, New UAMappingContext With { .EndpointDescriptor = endpointDescriptor, .NodeDescriptor = New UANodeDescriptor With { .BrowsePath = UABrowsePath.Parse("[ObjectsFolder]/Boilers/Boiler ", "http://opcfoundation.org/UA/Boiler/")}, .MonitoringParameters = 1000}) ' requested sampling interval (for subscriptions) - local OPC server Console.WriteLine() Console.WriteLine("Starting the simulation of the boiler in the server, using an OPC method call...") ' Currently there is no live mapping for OPC methods, therefore we call the OPC method in a traditional way. Try EasyUAClient.SharedInstance.CallMethod( endpointDescriptor, UABrowsePath.Parse("[ObjectsFolder]/Boilers/Boiler /Simulation", "http://opcfoundation.org/UA/Boiler/"), UABrowsePath.Parse("[nsu=http://opcfoundation.org/UA/Boiler/;i=1287].Start", "http://opcfoundation.org/UA/")) Catch e1 As UAException ' Production code would test the current state of the simulation first, and also handle the exception here. End Try Console.WriteLine() Console.WriteLine("Reading all data of the boiler...") mapper.Read() Console.WriteLine("Drum level is: {0}", boiler1.Drum.LevelIndicator.Output) Console.WriteLine() Console.WriteLine("Writing new setpoint value...") boiler1.LevelController.SetPoint = 50.0 Debug.Assert(boiler1.LevelController IsNot Nothing) mapper.WriteTarget(boiler1.LevelController, False) 'recurse: Console.WriteLine() Console.WriteLine("Subscribing to boiler data changes...") mapper.Subscribe(True) 'active: Thread.Sleep(30 * 1000) Console.WriteLine() Console.WriteLine("Unsubscribing from boiler data changes...") mapper.Subscribe(False) 'active: Console.WriteLine() Console.WriteLine("Press Enter to continue...") Console.ReadLine() End Sub End Class
The Boiler class:
// // Find all latest examples here: https://opclabs.doc-that.com/files/onlinedocs/OPCLabs-OpcStudio/Latest/examples.html . using System; using OpcLabs.BaseLib.LiveMapping; using OpcLabs.EasyOpc.UA; using OpcLabs.EasyOpc.UA.LiveMapping; namespace UAConsoleLiveMapping { // The Boiler and its constituents are described in our application domain terms, the way we want to work with them. // Attributes are used to describe the correspondence between our types and members, and OPC nodes. // This is how the boiler looks in OPC address space: // - Boiler #1 // - CC1001 (CustomController) // - ControlOut // - Description // - Input1 // - Input2 // - Input3 // - Drum1001 (BoilerDrum) // - LIX001 (LevelIndicator) // - Output // - FC1001 (FlowController) // - ControlOut // - Measurement // - SetPoint // - LC1001 (LevelController) // - ControlOut // - Measurement // - SetPoint // - Pipe1001 (BoilerInputPipe) // - FTX001 (FlowTransmitter) // - Output // - Pipe1002 (BoilerOutputPipe) // - FTX002 (FlowTransmitter) // - Output [UANamespace("http://opcfoundation.org/UA/Boiler/")] [UAType] class Boiler { // Specifying BrowsePath-s here only because we have named the class members differently from OPC node names. [UANode(BrowsePath = "/PipeX001")] public BoilerInputPipe InputPipe = new BoilerInputPipe(); [UANode(BrowsePath = "/DrumX001")] public BoilerDrum Drum = new BoilerDrum(); [UANode(BrowsePath = "/PipeX002")] public BoilerOutputPipe OutputPipe = new BoilerOutputPipe(); [UANode(BrowsePath = "/FCX001")] public FlowController FlowController = new FlowController(); [UANode(BrowsePath = "/LCX001")] public LevelController LevelController = new LevelController(); [UANode(BrowsePath = "/CCX001")] public CustomController CustomController = new CustomController(); } [UAType] class BoilerInputPipe { // Specifying BrowsePath-s here only because we have named the class members differently from OPC node names. [UANode(BrowsePath = "/FTX001")] public FlowTransmitter FlowTransmitter1 = new FlowTransmitter(); [UANode(BrowsePath = "/ValveX001")] public Valve Valve = new Valve(); } [UAType] class BoilerDrum { // Specifying BrowsePath-s here only because we have named the class members differently from OPC node names. [UANode(BrowsePath = "/LIX001")] public LevelIndicator LevelIndicator = new LevelIndicator(); } [UAType] class BoilerOutputPipe { // Specifying BrowsePath-s here only because we have named the class members differently from OPC node names. [UANode(BrowsePath = "/FTX002")] public FlowTransmitter FlowTransmitter2 = new FlowTransmitter(); } [UAType] class FlowController : GenericController { } [UAType] class LevelController : GenericController { } [UAType] class CustomController { [UANode, UAData(Operations = UADataMappingOperations.Write)] // not readable public double Input1 { get; set; } [UANode, UAData(Operations = UADataMappingOperations.Write)] // not readable public double Input2 { get; set; } [UANode, UAData(Operations = UADataMappingOperations.Write)] // not readable public double Input3 { get; set; } [UANode, UAData(Operations = UADataMappingOperations.ReadAndSubscribe)] // no OPC writing public double ControlOut { get; set; } [UANode, UAData] public string Description { get; set; } } [UAType] class FlowTransmitter : GenericSensor { } [UAType] class Valve : GenericActuator { } [UAType] class LevelIndicator : GenericSensor { } [UAType] class GenericController { [UANode, UAData(Operations = UADataMappingOperations.ReadAndSubscribe)] // no OPC writing public double Measurement { get; set; } [UANode, UAData] public double SetPoint { get; set; } [UANode, UAData(Operations = UADataMappingOperations.ReadAndSubscribe)] // no OPC writing public double ControlOut { get; set; } } [UAType] class GenericSensor { // Meta-members are filled in by information collected during mapping, and allow access to it later from your code. // Alternatively, you can derive your class from UAMappedNode, which will bring in many meta-members automatically. [MetaMember("NodeDescriptor")] public UANodeDescriptor NodeDescriptor { get; set; } [UANode, UAData(Operations = UADataMappingOperations.ReadAndSubscribe)] // no OPC writing public double Output { get => _output; set { _output = value; Console.WriteLine($"Sensor \"{NodeDescriptor}\" output is now {value}."); } } private double _output; } [UAType] class GenericActuator { [UANode, UAData(Operations = UADataMappingOperations.Write)] // generic actuator input is not readable public double Input { get; set; } } }
' ' Find all latest examples here: https://opclabs.doc-that.com/files/onlinedocs/OPCLabs-OpcStudio/Latest/examples.html . Imports OpcLabs.BaseLib.LiveMapping Imports OpcLabs.EasyOpc.UA Imports OpcLabs.EasyOpc.UA.LiveMapping ' The Boiler and its constituents are described in our application domain terms, the way we want to work with them. ' Attributes are used to describe the correspondence between our types and members, and OPC nodes. ' This is how the boiler looks in OPC address space: ' - Boiler #1 ' - CC1001 (CustomController) ' - ControlOut ' - Description ' - Input1 ' - Input2 ' - Input3 ' - Drum1001 (BoilerDrum) ' - LIX001 (LevelIndicator) ' - Output ' - FC1001 (FlowController) ' - ControlOut ' - Measurement ' - SetPoint ' - LC1001 (LevelController) ' - ControlOut ' - Measurement ' - SetPoint ' - Pipe1001 (BoilerInputPipe) ' - FTX001 (FlowTransmitter) ' - Output ' - Pipe1002 (BoilerOutputPipe) ' - FTX002 (FlowTransmitter) ' - Output <UANamespace("http://opcfoundation.org/UA/Boiler/"), UAType()> _ Friend Class Boiler ' Specifying BrowsePath-s here only because we have named the class members differently from OPC node names. <UANode(BrowsePath:="/PipeX001")> _ Public InputPipe As New BoilerInputPipe() <UANode(BrowsePath:="/DrumX001")> _ Public Drum As New BoilerDrum() <UANode(BrowsePath:="/PipeX002")> _ Public OutputPipe As New BoilerOutputPipe() <UANode(BrowsePath:="/FCX001")> _ Public FlowController As New FlowController() <UANode(BrowsePath:="/LCX001")> _ Public LevelController As New LevelController() <UANode(BrowsePath:="/CCX001")> _ Public CustomController As New CustomController() End Class <UAType()> _ Friend Class BoilerInputPipe ' Specifying BrowsePath-s here only because we have named the class members differently from OPC node names. <UANode(BrowsePath:="/FTX001")> _ Public FlowTransmitter1 As New FlowTransmitter() <UANode(BrowsePath:="/ValveX001")> _ Public Valve As New Valve() End Class <UAType()> _ Friend Class BoilerDrum ' Specifying BrowsePath-s here only because we have named the class members differently from OPC node names. <UANode(BrowsePath:="/LIX001")> _ Public LevelIndicator As New LevelIndicator() End Class <UAType()> _ Friend Class BoilerOutputPipe ' Specifying BrowsePath-s here only because we have named the class members differently from OPC node names. <UANode(BrowsePath:="/FTX002")> _ Public FlowTransmitter2 As New FlowTransmitter() End Class <UAType()> _ Friend Class FlowController Inherits GenericController End Class <UAType()> _ Friend Class LevelController Inherits GenericController End Class <UAType()> _ Friend Class CustomController <UANode(), UAData(Operations:=UADataMappingOperations.Write)> Public Property Input1 As Double <UANode(), UAData(Operations:=UADataMappingOperations.Write)> Public Property Input2 As Double <UANode(), UAData(Operations:=UADataMappingOperations.Write)> Public Property Input3 As Double <UANode(), UAData(Operations:=UADataMappingOperations.ReadAndSubscribe)> Public Property ControlOut As Double <UANode(), UAData()> Public Property Description As String End Class <UAType()> _ Friend Class FlowTransmitter Inherits GenericSensor End Class <UAType()> _ Friend Class Valve Inherits GenericActuator End Class <UAType()> _ Friend Class LevelIndicator Inherits GenericSensor End Class <UAType()> _ Friend Class GenericController <UANode(), UAData(Operations := UADataMappingOperations.ReadAndSubscribe)> Public Property Measurement As Double <UANode(), UAData()> Public Property SetPoint As Double <UANode(), UAData(Operations:=UADataMappingOperations.ReadAndSubscribe)> Public Property ControlOut As Double End Class <UAType()> _ Friend Class GenericSensor ' Meta-members are filled in by information collected during mapping, and allow access to it later from your code. ' Alternatively, you can derive your class from UAMappedNode, which will bring in many meta-members automatically. <MetaMember("NodeDescriptor")> Public Property NodeDescriptor As UANodeDescriptor <UANode(), UAData(Operations:=UADataMappingOperations.ReadAndSubscribe)> Public Property Output() As Double ' no OPC writing Get Return _output End Get Set(ByVal value As Double) _output = value Console.WriteLine("Sensor ""{0}"" output is now {1}.", NodeDescriptor, value) End Set End Property Private _output As Double End Class <UAType()> _ Friend Class GenericActuator <UANode(), UAData(Operations := UADataMappingOperations.Write)> Public Property Input As Double End Class
Copyright © 2004-2024 CODE Consulting and Development, s.r.o., Plzen. All rights reserved. Web page: www.opclabs.com
Send Documentation Feedback. Resources: Knowledge Base, Product Downloads. Technical support: Online Forums, FAQ.Missing some example? Ask us for it on our Online Forums! You do not have to own a commercial license in order to use Online Forums, and we reply to every post.